package de.playmee.geo;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.Spinner;
import android.widget.Toast;
import android.widget.LinearLayout.LayoutParams;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;

import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapActivity;
import com.google.android.maps.MapController;
import com.google.android.maps.MapView;
import com.google.android.maps.Overlay;

import de.playmee.Channel;
import de.playmee.Playmee;
import de.playmee.R;
import de.playmee.handler.ChannelHandler;
import de.playmee.handler.RegionHandler;
import de.playmee.views.SimpleSpinnerAdapter;

public class GeoActivity extends MapActivity implements OnTouchListener, OnSeekBarChangeListener, LocationListener, OnItemSelectedListener {
	// Tag for Log entries
	public static String TAG = "GeoActivity";
	
	// configuration of region
	public static float initialRadius = 20.f;
	public static int maxRegionRadius = 50000;
	// configuration of radius seek bar
	public static int slope = 3;
	
	public static int updateMinTime = 1000 * 30; // 30s
	
	// bubble images for drawing center of regions and current location
	private Bitmap bubble;
	private Bitmap bubbleLocation;
	
	// GUI elements, that maybe get changed
	private MapView mapView;
	private ImageButton addRegionButton, removeRegionButton, submitRegionButton, discardRegionButton0;
	private SeekBar radiusSeekBar;
	private LinearLayout finishScreen;
	private EditText titleTextField;
	private Spinner channelSelection;
	
	// variables for controlling region creation and changing
	private boolean newRegion = false, cleared = false, regionFound = false;
	private RadiusOverlay lastRegion = null;
	private float oldRadius = -1;
	private GeoPoint oldPosition = null;
	
	// region handler to read and write regions from (or to) the database
	private RegionHandler rHandler;
	private ChannelHandler cHandler;
	
	private LocationManager locationManager;
	private GeoPoint lastLocation = null;
	private BubbleOverlay lastLocationOverlay = null;
	
	private Channel selectedChannel = null;
	
	
	@Override
	protected boolean isRouteDisplayed() {
		return false;
	}
	
	/**
	 * onCreate initializes the map view by setting the gui elements
	 * and drawing all regions in the database
	 */
	@SuppressWarnings("deprecation")
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.geoview);
		
		Playmee application = (Playmee) getApplication();
		
		// get bubble images
		bubble = BitmapFactory.decodeResource(getResources(), R.drawable.bubble);
		bubbleLocation = BitmapFactory.decodeResource(getResources(), R.drawable.bubble2);
		
		// initialize gui elements
		mapView = (MapView) findViewById(R.id.mapview);
		addRegionButton = (ImageButton) findViewById(R.id.button_addRegion);
		removeRegionButton = (ImageButton) findViewById(R.id.button_removeRegion);
		discardRegionButton0 = (ImageButton) findViewById(R.id.button_discardRegionChanges0);
		submitRegionButton = (ImageButton) findViewById(R.id.button_submitRegion);
		radiusSeekBar = (SeekBar) findViewById(R.id.seekBar_radius);
		titleTextField = (EditText) findViewById(R.id.editText_regionTitle);
		finishScreen = (LinearLayout) findViewById(R.id.layout_finishRegion);
		channelSelection = (Spinner) findViewById(R.id.spinner_channel);
		
		// enabling predefined zoom functionality
		LinearLayout zoomLayout =(LinearLayout)findViewById(R.id.layout_zoom);  
		View zoomView = mapView.getZoomControls(); 
		zoomLayout.addView(zoomView, new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); 
		mapView.displayZoomControls(true);

		// set locationManager and lastLocation
		locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
		if (application.gpsLocationProviderIsEnabled() || application.networkLocationProviderIsEnabled()) {
			Location loc = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
			if (loc!=null) {
				lastLocation = new GeoPoint((int) (loc.getLatitude() * 1E6), (int) (loc.getLongitude() * 1E6));
				updateLocation();
			}
		}
		// request location updates (set LocationListener)
		locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, updateMinTime, 0, this);
		
		// setting listeners to several gui elements
		mapView.setOnTouchListener(this);
		radiusSeekBar.setOnSeekBarChangeListener(this);
		titleTextField.setOnTouchListener(this);
		
		// set region handler
		rHandler = application.getRegionHandler();
		cHandler = application.getChannelHandler();
		
		// set channel handler in priorityView
		//priorityView.setChannelHandler(((PlayMEE) getApplication()).getChannelHandler());
		initChannelSpinner(null);
		channelSelection.setOnItemSelectedListener(this);
		
		// display all existing region
		ArrayList<Region> regions = rHandler.loadRegions(null, null, null, null);
		for (int i=0; i<regions.size(); i++) {
			mapView.getOverlays().add(new RadiusOverlay(regions.get(i), bubble));
		}
		
		mapView.invalidate();
	}
	
	private void initChannelSpinner(Channel selectedChannel) {
		LinkedList<Channel> allChannels = new LinkedList<Channel>(cHandler.loadChannels(null, null, null, ChannelHandler.ORDER_BY_LABEL_ABC));
		if (selectedChannel != null) {
			allChannels.remove(selectedChannel);
			allChannels.add(0, selectedChannel);
		}
		SimpleSpinnerAdapter sAdapter = new SimpleSpinnerAdapter(getApplicationContext(), allChannels);
		channelSelection.setAdapter(sAdapter);
	}
	
	@Override
	protected void onStart() {
		super.onStart();
		// check if provider is available
		Playmee application = (Playmee) getApplication();
		if (!application.gpsLocationProviderIsEnabled()
				&& !application.networkLocationProviderIsEnabled()) {
			Log.i(TAG, "No provider available. Can't find current location.");
			// showInfoDialog();
			// TODO available when class extends MainMenuActivity
		}
	}


	/**
	 * touch listener for some gui elements
	 */
	public boolean onTouch(View v, MotionEvent event) {
		switch (v.getId()) {
		case R.id.mapview:
			// map is touched
			// get geo coordinates of touch point
			GeoPoint hitPoint = mapView.getProjection().fromPixels((int) event.getX(), (int) event.getY());

			// case 1: a new region must be created 
			if (event.getAction() == MotionEvent.ACTION_UP && newRegion && lastRegion == null) {
				Region addedRegion = new Region(hitPoint, initialRadius);
				if (intersectsRegion(addedRegion)!=-1) {
					Toast.makeText(getApplicationContext(), "A region should not intersect an other region", Toast.LENGTH_LONG).show();
				}
				else {
					submitRegionButton.setVisibility(View.VISIBLE);
					
					// create new region
					lastRegion = new RadiusOverlay(addedRegion, bubble);
					lastRegion.setMoveable(true);
					
					// add it to the map
					mapView.getOverlays().add(lastRegion);
					mapView.invalidate();
				}
			}
			// case 2: check if a region title is touched and there is no 'active' region
			//	if this is the case save it in lastRegion
			else if (event.getAction() == MotionEvent.ACTION_DOWN && !newRegion && lastRegion == null) {
				for (int i=0; i<mapView.getOverlays().size(); i++) {
					Overlay o = mapView.getOverlays().get(i);
					if (o instanceof RadiusOverlay) {
						RadiusOverlay radOver = (RadiusOverlay) o;
						if (radOver.getBubbleOverlay().labelBoxTouched(hitPoint, mapView)) {
							lastRegion = radOver;
							break;
						}
					}
				}
			}
			// case 3: check if the 'pressed' and 'released' region are the same
			//	if this is the case draw the edit bar
			else if (event.getAction() == MotionEvent.ACTION_UP&& !newRegion && lastRegion != null && !regionFound) {
				if (lastRegion.getBubbleOverlay().labelBoxTouched(hitPoint, mapView)) {
					regionFound = true;
					showEditBar();
				}
				else {
					lastRegion = null;
				}
				
			}
			break;
		case R.id.editText_regionTitle:
			clearTextField((EditText)v);
			break;
		}
		return false;
	}
	
	/**
	 * onProgessChanged controls the radius of the active region
	 */
	public void onProgressChanged(SeekBar seekBar, int progress,
			boolean fromUser) {
		if (!fromUser)
			return;
		switch (seekBar.getId()) {
		case R.id.seekBar_radius:
			// if there is an active region
			if (lastRegion != null) {
				// update its radius to the new value
				//	by computing the new radius out of the given progress
				float newRadius = getRadiusByProgress(seekBar.getProgress());
				Region newRegion = lastRegion.getRegion();
				newRegion.setMeters(newRadius);
				mapView.getOverlays().remove(mapView.getOverlays().size()-1);
				
				float ir = intersectsRegion(newRegion);
				if (ir==-1) {
					// update overlay on screen
					lastRegion.getRegion().setMeters(newRadius);
				}
				else {
					lastRegion.getRegion().setMeters(ir);
				}
				mapView.getOverlays().add(lastRegion);
				
				mapView.invalidate();
			}
			break;
		}
	}

	public void onStartTrackingTouch(SeekBar seekBar) {}
	public void onStopTrackingTouch(SeekBar seekBar) {}
	
	/**
	 * submitRegion button was clicked
	 * 	therefore finishScreen is activated
	 * @param view should be the submitRegion button
	 */
	public void submitRegionButtonClicked(View view) {
		// initialize finish screen
		Channel standardChannel = null;
		if (!newRegion) {
			// set title
			titleTextField.setText(lastRegion.getRegion().getTitle());
			// set priorityList
			standardChannel = lastRegion.getRegion().getChannel();
		}
		else {
			// titleTextField.setHint("Enter a title");
			titleTextField.setText("Enter a title");
		}
		//priorityView.initPriorityList(priorityList);
		initChannelSpinner(standardChannel);
		
		// display finishScreen
		finishScreen.setVisibility(View.VISIBLE);
		// set cleared (indicate whether the text field should be cleared after the first touch event)
		cleared = !newRegion;
		hideEditBar();
	}
	
	/**
	 * addRegion button was clicked
	 * 	therefore the edit bar is activated
	 * @param view should be the addRegion button
	 */
	public void addRegionButtonClicked(View view) {
		newRegion = true;
		lastRegion = null;
		showEditBar();
	}
	
	/**
	 * removeRegion button was clicked, and therefore 
	 * 	the selected region must be removed from screen and database
	 * @param view
	 */
	public void removeRegionButtonClicked(View view) {
		rHandler.deleteRegion(lastRegion.getRegion().getId());
		removeCurrentRegion();
		hideEditBar();
		reset();
		mapView.invalidate();
	}
	
	/**
	 * discardRegionChanges button was clicked
	 * 	therefore if previously a new region was created, this is going to be removed again
	 * 		and the edit bar is deactivated
	 * @param view should be the discardRegionChanges0 button
	 */
	public void discardRegionButton0Clicked(View view) {
		// if a new region was created
		//	-> delete it
		if (newRegion && lastRegion != null) {
			removeCurrentRegion();
		}
		else if (!newRegion) {
			lastRegion.getRegion().setMeters(oldRadius);
			lastRegion.getRegion().setCenter(oldPosition);
		}
		mapView.invalidate();
		hideEditBar();
		
		reset();
	}
	
	/**
	 * discardRegion button was clicked
	 * 	therefore all changes are reseted
	 * @param view should be the discardRegion button
	 */
	public void discardRegionButton1Clicked(View view) {
		// finishScreen is deactivated
		finishScreen.setVisibility(View.GONE);
		
		if (newRegion && lastRegion!=null) {
			removeCurrentRegion();
		}
		else {
			// reset radius
			if (oldRadius!=-1) {
				lastRegion.getRegion().setMeters(oldRadius);
			}
			// reset position
			if (oldPosition!=null) {
				lastRegion.getRegion().setCenter(oldPosition);
			}
		}
		
		reset();
	}
	
	/**
	 * storeRegion button was clicked
	 * 	therefore all new values are saved and stored into the database
	 * @param view should be the storeRegion button
	 */
	public void storeRegionButtonClicked(View view) {
		// get new title and priority list
		String title = titleTextField.getText().toString().trim();
		
		// if the title is not too short, and priority list is not empty
		//	save it
		if (title.length()>2 && title.length()<17 && selectedChannel!=null) {
			// get region from its overlay
			Region edit = lastRegion.getRegion();
			// set title, new radius and priorityList
			edit.setTitle(title);
			edit.setChannel(selectedChannel);
			
			// write it to database
			if (newRegion) {
				rHandler.insertRegion(lastRegion.getRegion());
			}
			else {
				rHandler.updateRegion(lastRegion.getRegion());
			}
			
			reset();
			finishScreen.setVisibility(View.GONE);
			return;
		}
		
		// else draw a message
		if (title.length()<3 || title.length()>16) {
			(Toast.makeText(getApplicationContext(), "The size of your title must be between 3 and 16", Toast.LENGTH_LONG)).show();
		}
		
		if (selectedChannel == null) {
			(Toast.makeText(getApplicationContext(), "You must select a channel!", Toast.LENGTH_LONG)).show();
		}
	}
	
	/**
	 * displays the edit bar
	 */
	private void showEditBar() {
		if (lastRegion!=null) {
			int progress = getProgressByRadius(lastRegion.getRegion().getMeters());
			radiusSeekBar.setProgress(progress);
			submitRegionButton.setVisibility(View.VISIBLE);
			removeRegionButton.setVisibility(View.VISIBLE);
			lastRegion.setMoveable(true);
			
			// store the old values (maybe it is needed for reconstruction the old state of the current region)
			oldRadius = lastRegion.getRegion().getMeters();
			oldPosition = lastRegion.getRegion().getCenter();
		}
		radiusSeekBar.setVisibility(View.VISIBLE);
		discardRegionButton0.setVisibility(View.VISIBLE);
		addRegionButton.setVisibility(View.GONE);
	}
	
	/**
	 * hides the edit bar
	 */
	private void hideEditBar() {
		if (lastRegion!=null) {
			lastRegion.setMoveable(false);
		}
		radiusSeekBar.setProgress(0);
		radiusSeekBar.setVisibility(View.GONE);
		submitRegionButton.setVisibility(View.GONE);
		discardRegionButton0.setVisibility(View.GONE);
		removeRegionButton.setVisibility(View.GONE);
		addRegionButton.setVisibility(View.VISIBLE);
	}
	
	/**
	 * sets variables indicating the current region to their initial values
	 */
	private void reset() {
		oldRadius = -1;
		oldPosition = null;
		newRegion = false;
		lastRegion = null;
		regionFound = false;
		selectedChannel = null;
	}
	
	/**
	 * removes the current region from the map view
	 */
	private void removeCurrentRegion() {
		mapView.getOverlays().remove(mapView.getOverlays().size()-1);
		mapView.invalidate();
	}
	
	/**
	 * removes the content of the given text field
	 * @param view
	 */
	public void clearTextField(EditText view) {
		if (!cleared) {
			view.setText("");
			cleared = true;
		}
	}
	
	protected void onResume() {
		super.onResume();
		updateLocation();
	}
	
	/**
	 * updates map center point
	 */
	private void updateLocation() {
		if (lastLocation!=null) {
			// update bubble overlay
			if (lastLocationOverlay==null) {
				lastLocationOverlay = new BubbleOverlay(bubbleLocation, "You are here!", lastLocation);
			}
			else {
				mapView.getOverlays().remove(lastLocationOverlay);
			}
			lastLocationOverlay.setPosition(lastLocation);
			lastLocationOverlay.setDrawLabel(true);
			mapView.getOverlays().add(lastLocationOverlay);
			
			MapController controller = mapView.getController();
			controller.animateTo(lastLocation);
			controller.setZoom(16);
		}
	}
	
	
	/**
	 * getRadiusByProgress computes the corresponding radius in meters
	 * @param progress value of the seekbar
	 * @return radius
	 */
	private float getRadiusByProgress(int progress) {
		return 20 + ((float)(maxRegionRadius/radiusSeekBar.getMax())) * ((float)(Math.pow((float)progress, slope)/Math.pow((float)radiusSeekBar.getMax(), slope-1)));
	}
	
	/**
	 * getProgressByRadius is the inverse function to getRadiusByProgress
	 * 	and therefore it computes the progess w.r.t. the given radius
	 * @param radius
	 * @return progress
	 */
	private int getProgressByRadius(float radius) {
		float x = (radius - 20.0f) / maxRegionRadius;
		x = (float) Math.pow(x, 1.0 / slope);
		x = radiusSeekBar.getMax() * x;
		return (int) x;
	}
	
//	private boolean isOnARegion(GeoPoint point) {
//		List<Region> allRegions = rHandler.loadRegions(null, null, null, null);
//		float latitude = (float) (point.getLatitudeE6() / 1E6),
//				longitude = (float) (point.getLongitudeE6() / 1E6); 
//		boolean res = false;
//		for (int i=0; i<allRegions.size(); i++) {
//			if (allRegions.get(i).isOnThisRegion(latitude, longitude)) {
//				res = true;
//				break;
//			}
//		}
//		return res;
//	}
	
	private float intersectsRegion(Region region) {
		List<Region> allRegions = rHandler.loadRegions(null, null, null, null);
		float res = -1;
		float d;
		for (int i=0; i<allRegions.size(); i++) {
			d = allRegions.get(i).getDistance(region.getCenter());
			if (d <= region.getMeters() + allRegions.get(i).getMeters()) {
				res = d - allRegions.get(i).getMeters() - 0.0001f;
				break;
			}
		}
		return res;
	}

	@Override
	public void onLocationChanged(Location location) {
		int latitude = (int) (location.getLatitude() * 1E6);
		int longitude = (int) (location.getLongitude() * 1E6);
		lastLocation = new GeoPoint(latitude, longitude);
		// TODO update marker
	}

	@Override
	public void onProviderDisabled(String provider) {
		// nothing to do
	}

	@Override
	public void onProviderEnabled(String provider) {
		// nothing to do
	}

	@Override
	public void onStatusChanged(String provider, int status, Bundle extras) {
		// nothing to do
	}

	@Override
	public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2,
			long arg3) {
		selectedChannel = (Channel) arg0.getItemAtPosition(arg2);
	}

	@Override
	public void onNothingSelected(AdapterView<?> arg0) {}
}